/**
 * Generate code from /{{projectName}}-core/src/main/java/{{packageName}}/core/{{modules}}/service/impl/{{entities@SQL}}ServiceImpl.java.hbs
 */
// @SkipOverwrite
package cn.ibizlab.core.system.service.impl;

import cn.ibizlab.core.authorization.domain.SysRole;
import cn.ibizlab.core.authorization.service.SysRoleService;
import cn.ibizlab.core.system.domain.SysDeploySystem;
import cn.ibizlab.core.system.domain.SysMenu;
import cn.ibizlab.core.system.filter.SysDeploySystemSearchContext;
import cn.ibizlab.core.system.mapping.ResourceMapping;
import cn.ibizlab.util.common.CacheStore;
import cn.ibizlab.util.helper.JacksonUtils;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.cache.annotation.Caching;
import org.springframework.stereotype.Service;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.tree.Tree;
import cn.hutool.core.lang.tree.TreeNode;
import cn.hutool.core.lang.tree.TreeNodeConfig;
import cn.hutool.core.lang.tree.TreeUtil;
import cn.ibizlab.core.system.domain.*;
import org.springframework.util.DigestUtils;
import org.springframework.util.ObjectUtils;

import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.List;
import java.util.stream.Collectors;

/**
 * 实体[系统] 服务对象接口实现
 */
@Slf4j
@Service("SysDeploySystemService")
public class SysDeploySystemServiceImpl extends AbstractSysDeploySystemService {

    @Autowired
    private ResourceMapping resourceMapping;

    private final static String CUSTOM_RESOURCE = "CUSTOM_RESOURCE";

    private final static String OTHER_API = "OTHAPI";

    @Override
    public SysDeploySystem get(SysDeploySystem et) {
        super.get(et);
        if(et.getComponents()!=null)
            et.setCustomResources(resourceMapping.Component2CustomResource(et.getComponents().stream().filter(comp->comp.getCodeName()!=null&&comp.getCodeName().startsWith(CUSTOM_RESOURCE)).collect(Collectors.toList())));
        return et;
    }

    private void mergeCustomResource(SysDeploySystem et) {
        if(et.getCustomResources() == null)
            return;
        if(et.getComponents()==null)
            et.setComponents(new ArrayList<>());
        List<SysComponent> list = resourceMapping.customResource2Component(et.getCustomResources());
        String rootCustomResourceId = String.format("%1$s-CSTMRES",et.getSystemId());
        if(!list.isEmpty() && list.stream().noneMatch(u->rootCustomResourceId.equals(u.getId())))
            list.add(0,new SysComponent().setId(rootCustomResourceId).setDisplayName("自定义资源").setCodeName(CUSTOM_RESOURCE).setComponentType("UNIRES").setParentId("").setSort(92000L));
        list.forEach(item->{
            if(ObjectUtils.isEmpty(item.getParentId()) && !rootCustomResourceId.equals(item.getId()))
                item.setParentId(rootCustomResourceId);
        });
        et.setComponents(et.getComponents().stream().filter(comp->comp.getCodeName()==null||!comp.getCodeName().startsWith(CUSTOM_RESOURCE)).collect(Collectors.toList()));
        et.getComponents().addAll(list);
    }

    @Override
    @Cacheable(value = "sysdeploysystem", key = "'dc:'+#p0.dc")
    public List<SysDeploySystem> all(SysDeploySystem dto) {
        return this.listDefault(new SysDeploySystemSearchContext());
    }

    @Override
    @Caching(evict = {
            @CacheEvict(value = "sysdeploysystem", key = "'dc:'+#p0.dc"),
            @CacheEvict(value = "sysdeploysystem", key = "'dc:'"),
            @CacheEvict(value = "sysdeploysystem", key = "'dc:null'"),
            @CacheEvict(value = "sysdeploysystem", key = "'row:'+#p0.id")
    })
    public boolean create(SysDeploySystem et) {
        mergeCustomResource(et);
        return super.create(et);
    }


    @Override
    public Boolean deploy(SysDeploySystem dto) {
        if(!ObjectUtils.isEmpty(dto.getId())&&ObjectUtils.isEmpty(dto.getSystemId()))
            dto.setSystemId(dto.getId());
        return super.save(dto);
    }

    @Override
    @Caching(evict = {
            @CacheEvict(value = "sysdeploysystem", key = "'dc:'+#p0.dc"),
            @CacheEvict(value = "sysdeploysystem", key = "'dc:'"),
            @CacheEvict(value = "sysdeploysystem", key = "'dc:null'"),
            @CacheEvict(value = "sysdeploysystem", key = "'row:'+#p0.id")
    })
    public boolean update(SysDeploySystem et) {
        if(et.getCustomResources() != null) {
            if (ObjectUtils.isEmpty(et.getComponents()))
                et.setComponents(this.getById(et.getId()).getComponents());
            mergeCustomResource(et);
        }
        if(et.getBindingId() != null)
            CacheStore.getInstance().clearL2ByPattern("ibiz-cloud-clouddata-*--DCSystem");
        return super.update(et);
    }

    @Override
    @CacheEvict(value = "sysdeploysystem", allEntries = true)
    public boolean remove(SysDeploySystem et) {
        return super.remove(et);
    }

    @Override
    public boolean save(SysDeploySystem et) {
        boolean ret=false;
        if(!ObjectUtils.isEmpty(et.getDc()))
            et.setId((String)et.getDefaultKey(true));
        SysDeploySystem digest = new SysDeploySystem().setDisplayName(et.getDisplayName()).setApis(et.getApis()).setMenus(et.getMenus()).setComponents(et.getComponents());
        String md5Checksum = DigestUtils.md5DigestAsHex(JacksonUtils.toJson(digest).getBytes(StandardCharsets.UTF_8));
        et.setMd5Checksum(md5Checksum);
        SysDeploySystem check=this.getOne(Wrappers.<SysDeploySystem>lambdaQuery().select(SysDeploySystem::getMd5Checksum,SysDeploySystem::getComponents,SysDeploySystem::getId).eq(SysDeploySystem::getId,et.getId()),false);
        if(check!=null) {
            if(!ObjectUtils.isEmpty(check.getMd5Checksum())&&md5Checksum.equalsIgnoreCase(check.getMd5Checksum())) {
                log.info("系统{}-{}的配置未发生变化，忽略部署！",et.getSystemId(),et.getDisplayName());
                et.set("deploy_info","配置未发生变化，忽略部署");
                return true;
            }
            if(!ObjectUtils.isEmpty(check.getComponents()))
                et.setCustomResources(resourceMapping.Component2CustomResource(check.getComponents().stream().filter(comp->comp.getCodeName()!=null&&comp.getCodeName().startsWith(CUSTOM_RESOURCE)).collect(Collectors.toList())));
            ret = getSelf().update(et);
        }
        else
            ret = getSelf().create(et);
        if(ret) {
            SysRole role=new SysRole();
            role.setDeploySystem(et).setDeploySystemId(et.getId()).setName(String.format("%1$s 用户",et.getDisplayName())).setAuthority(String.format("ROLE_%1$s_USERS",et.getSystemId().toUpperCase())).setId((String)role.getDefaultKey(true));
            sysRoleService.save(role);

            role=new SysRole();
            role.setDeploySystem(et).setDeploySystemId(et.getId()).setName(String.format("%1$s 管理员",et.getDisplayName())).setAuthority(String.format("ROLE_%1$s_ADMIN",et.getSystemId().toUpperCase())).setId((String)role.getDefaultKey(true));
            sysRoleService.save(role);
        }
        return ret;
    }

    /**
     * 构造菜单、功能、组件、接口权限树行为
     * @param deploySystemId
     * @return
     */
    @Override
    public SysDeploySystem getPermissions(String deploySystemId) {

        SysDeploySystem deploySystem = get(deploySystemId);
        List<SysMenu> menus =  deploySystem.getMenus();
        List<SysComponent> components = deploySystem.getComponents();
        List<SysApi> apis = deploySystem.getApis();
        //构建node列表
        List<TreeNode<String>> permissionTreeNodes = CollUtil.newArrayList();
        Map<String,SysApi> apiMap = new HashMap<>();
        Map<String,SysApiOperation> apiOperationCheckMap = new LinkedHashMap<>();
        Map<String,SysApiOperation> apiOperationMap = new HashMap<>();
        Map<String,SysComponent> componentMap = new HashMap<>();
        //构建api
        if(!ObjectUtils.isEmpty(apis)){
            apis.forEach(api->{
                apiMap.put(api.getId(),api);
                if(!ObjectUtils.isEmpty(api.getOperations()))
                    api.getOperations().forEach(apiOperation->{
                        apiOperationCheckMap.put(apiOperation.getId(),apiOperation);
                        apiOperationMap.put(apiOperation.getId(),apiOperation);
                    });
            });
        }
        TreeNode uniRes = null;
        TreeNode cstmRes = null;
        //构建组件
        if(!ObjectUtils.isEmpty(components)){
            for(SysComponent component : components) {
                componentMap.put(component.getId(),component);
                //构造子部件节点，关联父部件，父部件则由菜单项构造
                if(!ObjectUtils.isEmpty(component.getParentId()))
                    permissionTreeNodes.add(new TreeNode<>(component.getId(), component.getParentId(), component.getDisplayName()+"  -  "+component.getComponentType(), 5).setExtra(new HashMap() {{put("type", component.getComponentType());}}));
                else if(CUSTOM_RESOURCE.equals(component.getCodeName()))
                    cstmRes =  new TreeNode<>(component.getId(), "0", component.getDisplayName()+"  -  CSTMRES", 1).setExtra(new HashMap() {{put("type", "CSTMRES");}});
                else if("UNIRES".equals(component.getComponentType()))
                    uniRes =  new TreeNode<>(component.getId(), "0", component.getDisplayName()+"  -  "+component.getComponentType(), 1).setExtra(new HashMap() {{put("type", component.getComponentType());}});


                List<SysComponentApi> componentApis = component.getApis();
                if(!ObjectUtils.isEmpty(componentApis)) {
                    componentApis.forEach(componentApi->{
                        if (!apiOperationMap.containsKey(componentApi.getApiOperationId()))
                            return;
                        SysApiOperation apiOperation = apiOperationMap.get(componentApi.getApiOperationId());
                        apiOperationCheckMap.remove(componentApi.getApiOperationId());
                        String newOperationId=DigestUtils.md5DigestAsHex((component.getId()+apiOperation.getId()).getBytes(StandardCharsets.UTF_8));
                        permissionTreeNodes.add(new TreeNode<>(newOperationId, component.getId(), apiOperation.getNotes()+"  -  API", 5).setExtra(new HashMap() {{put("type", "API");}}));

                        //构造数据范围
                        if(!ObjectUtils.isEmpty(apiOperation.getScopes()))
                            apiOperation.getScopes().forEach(dataScope -> permissionTreeNodes.add(new TreeNode<>(newOperationId+":"+dataScope.getId(), newOperationId, dataScope.getDisplayName(), 5).setExtra(new HashMap(){{put ("type","OPPRIV");}})));
                    });
                }
            }
        }
        //构建菜单
        if(!ObjectUtils.isEmpty(menus)){
            menus.forEach(menu -> {
                List<TreeNode<String>> menuItemTreeNodes = buildMenuItemNodes(menu,componentMap);
                if(!ObjectUtils.isEmpty(menuItemTreeNodes)){
                    permissionTreeNodes.addAll(menuItemTreeNodes);
                }
            });
        }
        if(uniRes!=null)
            permissionTreeNodes.add(uniRes);
        if(cstmRes!=null)
            permissionTreeNodes.add(cstmRes);

        if(apiOperationCheckMap.size()>0) {
            permissionTreeNodes.add(new TreeNode<>(deploySystem.getSystemId()+"-API", "0", "其他接口", 1).setExtra(new HashMap() {{put("type", "API");}}));
            Map<String,TreeNode> apiCheck=new LinkedHashMap<>();
            for(SysApiOperation apiOperation:apiOperationCheckMap.values()) {
                if("GetDraft".equalsIgnoreCase(apiOperation.getCodeName())||"CheckKey".equalsIgnoreCase(apiOperation.getCodeName()))
                    continue;
                if(!apiCheck.containsKey(apiOperation.getApiId())) {
                    SysApi api = apiMap.get(apiOperation.getApiId());
                    TreeNode apiTreeNode = new TreeNode<>(api.getId()+"-"+OTHER_API, deploySystem.getSystemId()+"-API", api.getTags() , 5).setExtra(new HashMap() {{put("type", "API");}});
                    permissionTreeNodes.add(apiTreeNode);
                    apiCheck.put(apiOperation.getApiId(),apiTreeNode);
                }
                permissionTreeNodes.add(new TreeNode<>(apiOperation.getId()+"-"+OTHER_API, apiOperation.getApiId()+"-"+OTHER_API, apiOperation.getNotes()+"  -  API", 5).setExtra(new HashMap() {{put("type", "API");}}));
                //构造数据范围
                if(!ObjectUtils.isEmpty(apiOperation.getScopes()))
                    apiOperation.getScopes().forEach(dataScope -> permissionTreeNodes.add(new TreeNode<>(OTHER_API+":"+dataScope.getId(), apiOperation.getId()+"-"+OTHER_API, dataScope.getDisplayName(), 5).setExtra(new HashMap(){{put ("type","OPPRIV");}})));
            }
        }
        //构建菜单、应用功能、组件、接口权限树
        if(!ObjectUtils.isEmpty(permissionTreeNodes)){
            //配置
            TreeNodeConfig treeNodeConfig = new TreeNodeConfig();
            //转换器
            List<Tree<String>> treeNodes = TreeUtil.build(permissionTreeNodes, "0", treeNodeConfig,
                    (treeNode, tree) -> {
                        tree.setId(treeNode.getId());
                        tree.setParentId(treeNode.getParentId());
                        tree.putExtra("type",treeNode.getExtra().get("type"));
                        tree.putExtra("display_name", treeNode.getName());
                    });
            deploySystem.setPermissions(JacksonUtils.toArray(JacksonUtils.toJson(treeNodes),SysPermission.class));
        }
        deploySystem.setComponents(null);
        deploySystem.setApis(null);
        deploySystem.setMenus(null);
        return deploySystem;
    }

    /**
     * 构造菜单树节点对象
     * @param menu
     * @param componentMap
     * @return
     */
    protected List<TreeNode<String>> buildMenuItemNodes(SysMenu menu , Map<String,SysComponent> componentMap){

        List<TreeNode<String>> menuItemTreeNodes = new ArrayList<>();

        TreeNode menuTreeNode = new TreeNode<>(menu.getId(), ObjectUtils.isEmpty(menu.getParentId())?  "0": menu.getParentId(), menu.getDisplayName(), 5);
        menuTreeNode.setExtra(new HashMap(){{put ("type","APPMENU");}});
        menuItemTreeNodes.add(menuTreeNode);

        //应用功能
        SysMenuFunc menuFunc =  menu.getFunc();
        if(menuFunc != null){
            //应用功能引用的部件
            String strComponentId = menuFunc.getComponentId();
            if(ObjectUtils.isEmpty(strComponentId) || !componentMap.containsKey(strComponentId)){
                return menuItemTreeNodes;
            }
            //构造部件树节点
            SysComponent component = componentMap.get(strComponentId);
            TreeNode componentTreeNode  = new TreeNode<>(component.getId(), menuFunc.getMenuId(), component.getDisplayName()+"  -  "+component.getComponentType(), 5);
            componentTreeNode.setExtra(new HashMap(){{put ("type","VIEW");}});
            menuItemTreeNodes.add(componentTreeNode);
        }

        //子菜单
        if(!ObjectUtils.isEmpty(menu.getChildren())){
            for(SysMenu subMenu : menu.getChildren()){
                List<TreeNode<String>> subTreeNodes =  buildMenuItemNodes(subMenu,componentMap);
                if(!ObjectUtils.isEmpty(subTreeNodes)){
                    menuItemTreeNodes.addAll(subTreeNodes);
                }
            }
        }
        return  menuItemTreeNodes;
    }

    @Override
    @Caching(evict = {
            @CacheEvict(value = "sysdeploysystem", key = "'dc:'+#p0.dc"),
            @CacheEvict(value = "sysdeploysystem", key = "'dc:'"),
            @CacheEvict(value = "sysdeploysystem", key = "'dc:null'"),
            @CacheEvict(value = "sysdeploysystem", key = "'row:'+#p0.id")
    })
    public SysDeploySystem cacheEvict(SysDeploySystem dto) {
        return super.cacheEvict(dto);
    }

}